package com.hucc.datasource.core;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;

public class HuccConnectionPool {

    /**
     * 连接容器
     */
    private LinkedBlockingDeque<HuccConnection> pool;

    /**
     * 活跃的连接数量
     */
    private volatile int activeNum = 0;

    /**
     * 驱动类
     */
    private String driverClassName;

    /**
     * 连接的数据库url
     */
    private String url;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 最大的连接数
     */
    private Integer maxActive = 8;

    /**
     * 最大的空闲连接
     */
    private Integer maxIdle = 8;

    /**
     * 最小的空闲连接数
     */
    private Integer minIdle = 0;

    /**
     * 获得连接最大的等待时间 单位毫秒
     */
    private Long maxWait;

    /**
     * 获取连接时，是否测试连接的有效性
     */
    private Boolean testOnBorrow = false;

    /**
     * 归还连接时，是否验证连接的有效性
     */
    private Boolean testOnReturn = false;

    /**
     * 验证连接有效性sql
     */
    private String validationQuery;

    /**
     * 验证sql执行的超时时间
     */
    private Long validationQueryTimeout;

    /**
     * 空闲连接销毁任务执行的间隔时间
     */
    private Long timeBetweenEvictionRunsMillis;

    /**
     * 空闲连接销毁任务验证的个数
     */
    private Integer numTestsPerEvictionRun = 3;

    /**
     *  空闲连接销毁的阀值 单位毫秒
     */
    private Long minEvictableIdleTimeMillis = 30 * 60 * 1000L;

    /**
     * 空闲连接销毁是否验证连接有效性
     */
    private Boolean testWhileIdle;

    private HuccConnectionFactory connectionFactory;

    public HuccConnectionPool() {
    }

    public HuccConnectionPool(String driverClassName, String url, String username, String password, Integer maxActive,
                              Integer maxIdle, Integer minIdle, Long maxWait, Boolean testOnBorrow, Boolean testOnReturn,
                              String validationQuery, Long validationQueryTimeout, Long timeBetweenEvictionRunsMillis,
                              Integer numTestsPerEvictionRun, Long minEvictableIdleTimeMillis, Boolean testWhileIdle) {
        this.driverClassName = driverClassName;
        this.url = url;
        this.username = username;
        this.password = password;
        this.maxActive = maxActive;
        this.maxIdle = maxIdle;
        this.minIdle = minIdle;
        this.maxWait = maxWait;
        this.testOnBorrow = testOnBorrow;
        this.testOnReturn = testOnReturn;
        this.validationQuery = validationQuery;
        this.validationQueryTimeout = validationQueryTimeout;
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
        this.testWhileIdle = testWhileIdle;
    }

    /**
     * 初始化连接
     * @param initSize 初始化连接大小
     */
    public void initConnection(Integer initSize) {

        if (maxActive == null || maxActive <= 0) {
            maxActive = 8;
        }
        this.pool = new LinkedBlockingDeque(maxActive);

        if (initSize != null && initSize > 0) {
            for (int i = 0; i < initSize && i < maxActive; i++) {
                addConnection();
            }
        }
    }

    /**
     * 添加连接
     */
    private void addConnection() {
        HuccConnection connection = connectionFactory.createConnection();
        addConnection(connection);
    }

    /**
     * 添加连接
     */
    private void addConnection(HuccConnection connection) {
        connection.setLastTime(System.currentTimeMillis());
        pool.add(connection);
    }

    /**
     * 借出连接
     */
    public synchronized Connection borrowConnection() {
        HuccConnection connection = null;
        if (activeNum >= maxActive) {
            try {
                connection = pool.poll(maxWait, TimeUnit.MILLISECONDS);
                if (connection == null) {
                    throw new RuntimeException("获取连接超时");
                }
            } catch (InterruptedException e) {
                throw new RuntimeException("获取失败");
            }
        } else {
            if (activeNum < pool.size()) {
                connection = pool.remove();
            } else {
                connection = connectionFactory.createConnection();
            }
        }

        if (validateBorrowConnection(connection)) {
            activeNum++;
            return connection;
        } else {
            connectionFactory.destroyConnection(connection);
            return borrowConnection();
        }
    }

    private boolean validateBorrowConnection(Connection connection) {
        if (testOnBorrow != null && testOnBorrow && validationQuery != null && validationQuery.length() > 0) {
            return validateConnection(connection);
        }

        return true;
    }

    private boolean validateReturnConnection(Connection connection) {
        if (testOnReturn != null && testOnReturn && validationQuery != null && validationQuery.length() > 0) {
            return validateConnection(connection);
        }

        return true;
    }

    private boolean validateConnection(Connection connection) {
        if (validationQuery == null || validationQuery.length() == 0) {
            return true;
        }

        Statement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.createStatement();
            resultSet = statement.executeQuery(validationQuery);
            return resultSet.next();
        } catch (SQLException e) {
            return false;
        } finally {
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            if (resultSet == null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 归还连接
     * @param connection
     */
    public void returnConnection(Connection connection) {
        if (maxIdle.intValue() == pool.size()) {
            connectionFactory.destroyConnection(connection);
            activeNum--;
            return;
        }

        if (!validateReturnConnection(connection)) {
            connectionFactory.destroyConnection(connection);
            activeNum--;
            return;
        }

        addConnection((HuccConnection) connection);
        activeNum--;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(Integer maxActive) {
        this.maxActive = maxActive;
    }

    public Integer getMaxIdle() {
        return maxIdle;
    }

    public void setMaxIdle(Integer maxIdle) {
        this.maxIdle = maxIdle;
    }

    public Integer getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(Integer minIdle) {
        this.minIdle = minIdle;
    }

    public Long getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(Long maxWait) {
        this.maxWait = maxWait;
    }

    public Boolean getTestOnBorrow() {
        return testOnBorrow;
    }

    public void setTestOnBorrow(Boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public Boolean getTestOnReturn() {
        return testOnReturn;
    }

    public void setTestOnReturn(Boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public Long getValidationQueryTimeout() {
        return validationQueryTimeout;
    }

    public void setValidationQueryTimeout(Long validationQueryTimeout) {
        this.validationQueryTimeout = validationQueryTimeout;
    }

    public Long getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    public void setTimeBetweenEvictionRunsMillis(Long timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
        if (timeBetweenEvictionRunsMillis != null && timeBetweenEvictionRunsMillis > 0) {
            new Timer().schedule(new IdleDestroyTask(), timeBetweenEvictionRunsMillis);
        }
    }

    public Integer getNumTestsPerEvictionRun() {
        return numTestsPerEvictionRun;
    }

    public void setNumTestsPerEvictionRun(Integer numTestsPerEvictionRun) {
        this.numTestsPerEvictionRun = numTestsPerEvictionRun;
    }

    public Long getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }

    public void setMinEvictableIdleTimeMillis(Long minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public Boolean getTestWhileIdle() {
        return testWhileIdle;
    }

    public void setTestWhileIdle(Boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    /**
     * 空闲连接销毁任务
     */
    private class IdleDestroyTask extends TimerTask {

        @Override
        public void run() {
            try {
                Integer numTestsPerEvictionRun = getNumTestsPerEvictionRun();
                if (numTestsPerEvictionRun != null && numTestsPerEvictionRun > 0) {
                    for (int i = 0; i < numTestsPerEvictionRun && i < pool.size(); i++) {
                        HuccConnection connection = pool.getFirst();
                        if (System.currentTimeMillis() - connection.getLastTime() >= getMinEvictableIdleTimeMillis()) {
                            connectionFactory.destroyConnection(connection);
                        }

                        if (!connection.isClosed()) {
                            if (testWhileIdle) {
                                if (validateConnection(connection)) {
                                    pool.add(connection);
                                }
                            }
                        }
                    }
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
